상속보다는 합성을 사용해야 하는 이유

Posted by Yungwang Ryu on 2019-07-31

들어가며

상속은 막연히 부모 자식 관계 합성은 Setter 메소드 처럼 객체를 주입받는 패턴 정도만 알고 있었는데 “오브젝트 - 조영호” 책을 읽으면서 내용을 학습하여 이해하고자 정리 합니다. 그러면 먼저 상속과 합성이 무엇인지 부터 제가 깨닳은 내용을 공유해 드리죠

왜 상속보단 합성을 써야 할 까?

공통 역활을 수행하는 부모 클래스를 두고 부모 기능을 더하여 각자에 기능을 수행하는 자식클래스를 사용하는게 잘 못된다고 말하는것은 아니었습니다. 책에서 말하는 상속에 위험성은 2가지 였습니다.

상속은 캡슐화가 약화된다.

캡슐화 에 대한 설명은 따로 포스팅하였습니다~!

부모 클래스에 구현이 자식 클래스에게 노출되기 때문에 캡슐화가 약화된다.
캡슐화의 약화는 자식 클래스가 부모 클래스에 강하게 결합되도록 만들기 때문에 부모 클래스를 변경할 때 자식 클래스도 함께 변경될 확률을 높인다. 결과적으로 상속을 과도하게 사용한 코드는 변경하기도 어려워진다.

저는 이렇게 이해했습니다.
상속은 캡슐화를 위반한다. -> 상속을 하면 캡슐화를 위반할 확률이 높아진다. 또는 상속을 하려면 캡슐화를 떠 신경써서 잘 해야 한다!!

하지만, 캡슐화를 잘 시켜놓고 사용하면 좋은데 부모 클래스에 캡슐화가 잘 되지 않는다면 어떻게 될까요?

다시 말해서 변경 될 수 있는 부분은 감추면서 부모 클래스가 수행해야 할 역활과 책임을 온전히 처리하지 못하면 자식 클래스는 부모 클래스에 역활을 무자비하게 사용하여 강한 의존성을 가질수 있게 됩니다. 상황을 예로 들어 볼까요?

변경이 엄청 어려움

캡슐화 되지 못한 부모 클래스에 자식 클래스가 100개 있다고 가정해 보죠.
각 자식클래스는 신난다~~ 하고 부모 클래스에서 감추고 싶은 부분을 마음데로 접근하고 사용 하기 시작합니다. 코드를 보시죠

1
2
3
4
5
6
7
8
9
10
11
public abstract class RectangleSuperClass {

private int left;
private int top;
private int right;
private int bottom;

// construcotr..

// getter, setter ..
}

부모 객체가 할 일을 자식 객체에서 일일이 이렇게 구현하는게 맞을까요?

1
2
3
4
5
6
7
8
9
public class RectangleSubClass extends RectangleSuperClass {

// any method

public void anyMethod(int multiple) {
super.setRight(super.getRight() * multiple);
super.setBottom(super.getBottom() * multiple);
}
}

각 자식 클래스에서 사각형에 가로, 세로를 증가시키는 코드가 있다고 해보죠.
근데 부모 클래스에서 사각형의 넓이를 변경하는 변수명을 right, bottom이 아니라 length, hight로 바꿔야 겠다고 하면 어떻게 될까요?? 바꾸고 저장하면 100개의 클래스에서 빨간줄이 주르르르륵 뜰것입니다.

그 빨간줄이 많으면 많을수록 그만큼 코드 의존도가 높다고 할 수 있는것이죠.

그 외 문제점은 코드 중복입니다. 부모 객체에서 사각형을 크기를 핸들링하는 메소드를 만들고 자식클래스에서 사용하라고 했다면 자식클래스 입장에서는 함수만 쓰기만 하면 됩니다. 위 코드 처럼 setRight, setBottom 를 신경쓸필요 없다는 것이죠.

바로 이 맥락이 부모 클래스가 캡슐화 되지 못해서 벌어지는 현상을 보고 계시고 있는겁니다.

부모객체가 변경될수 있는 부분(구현)은 감추고 함수만 호출(인터페이스)하도록 하여 변경에 코드 영향도를 최소화 시켜야 되는데 그러지 못해서 코드변경이 일어 날 때마나 자식 객체까지 영향을 미치게 되는 것이죠.
자식 객체는 잘못 없는겁니다. 왜냐하면 부모 객체에서 접근 할 수 있도록 해줬거든요… 따라서 캡슐화는 곧 코드 변경에 코드 영향도를 최소화 하기 위해 존재한다고 볼 수 있겠네요.

그래서 어떻게 수정해야 되냐!

1
2
3
4
5
6
7
8
9
public abstract class RectangleSuperClass {

// ...

protected void changeRectangle(int multiple) {
this.right = multiple;
this.bottom = multiple;
}
}

이렇게 하면 자식 객체는 3만큼 사각형을 늘려줘!! 라고 부모 객체에게 메세지를 전달만 하면 되는 것입니다.

정리하면 상속을 쓰려면 부모 객체를 캡슐화를 잘해야 한다. 안그러면 자식 객체마다 코드 중복과 의존성이 강하게 결합되어 변경이 어렵다!! 가 되겠네요.

설계가 유연하지 않게된다.

상속을 사용한 객체 설계는 실행시점에서 코드가 유연하지 못하는다는 것입니다.
무슨 말이냐 우선 아래 상속 관계를 보죠

출처: 이미지는 오브젝트 | 조영호

이러한 상속 관계를 가질때 코드 실행중에 금액 할인 정책에서 비율 할인 정책으로 바꾸려면 어떻게 해야 할까요??
금액 할인 정책을 가지고 있는 AmountDiscountMovie 객체에는 각 상태값인 컨텍스트 정보가 있는데 바로 그냥 PercentDiscountMovie 객체로 스위칭 될까요? 한다 하더라도 AmountDiscountMovie에 컨텍스트 정보는 사라질것입니다.

위 코드 처럼 객체를 생성해서 각 컨텍스트 정보르 일일이 넣어 줘야하죠 …

바로 그래서 코드 실행시점에서도 유연하게 정책을 바꿀수 있는 방법이 합성인것입니다.

합성은 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법

위와 같이 하게 되면 실행중에도 얼마든지 changeDiscountPolicy 메소드를 호출하여 할인 정책을 스위칭 할 수 있습니다.
이것은 마치 Spring 에서 DI와 같은 개념이라 생각합니다. 이러한 이유 때문에 “주입” 을 통하여 실행중에도 유연하게 기능을 사용 할 수 있다고 생각합니다.